#!/bin/bash
#########################################################################################
# 本脚本属于 archlive 项目一部分  作者  Carbon Jiao  (Email:  carbonjiao alt gmail dot com)
# 详情 http://archlive.googlecode.com http://archlive-pkg.googlecode.com
#
# 本工具可以在任何符合条件的linux系统上构建基于Arch GNU/Linux包管理系统pacman的live系统——
# 可以是桌面环境、专业服务器甚至是防火墙。mkarchlive给你充分定制机会。
#
# mkarchlive 可以运行于符合如下条件的linux系统中：
# A、目标镜像类型为iso时，宿主系统需要安装3.72以上版本的syslinux;
# B、宿主系统需要安装如下常用软件: cdrkit(mkisofs) wget tar links shell
#   sed grep gawk module-init-tools(depmod) util-linux-ng(mount) findutils(find)
#   coreutils(ls, cut, tr, echo, rm, mkdir, cp, cat, chown, chmod, head, mknod, tee, touch, ln, md5sum)
#
# 支持i686 x86_64的目标系统创建, 理论上还支持mips，只要有对应软件仓
# （i686平台可以创建x86_64 Archlive, 反之亦准备完毕.
# 制作iso镜像时，无论使用什么bootloader，宿主系统都需要syslinux中的isohybrid程序来产生iso的md5sum
#
# 支持全方位定制： 定制启动脚本、定制目标系统软件、定制软件仓、预配置文件。。。
#########################################################################################
# 2011-04-01 改变判断宿主系统是否存在命令的方式；统一 mirrorlist, pacman.conf
# 2010-11-20 整合配置文件指定和参数判断
# 2010-11-19 细化软件清单内容
# 2010-11-16 加入单独制作overlay模块, 以便重复制作时，单独更新overlay内容（需要在配置文件夹中修改，在工作目录中直接修改会被覆盖）
# 2010-06-11 将双模式嵌入到每个步骤, 同一在制作配置文件中或者参数 -A 来设置
# 2010-06-10 添加双模式制作(i686和x86_64同存在于一个iso或者img中)
# 2010-02-20 添加功能：将devel独立出来（/usr/src等）
# 2010-02-12 修改iso目录结构，使得使用UltraISO U+方式正确运行
# 2010-01-12 使用Busybox代替klibc 启动，简化掉基本模块
# 2009-11-25 更新说明相关, 减少岐义; 删除文件、文件夹罗列到删除清单
# 2009-10-15 加入覆盖制作过程中对软件组的判断
# 2009-09-22 修改目标系统内核版本判断方式，加快速度。普通用户可以使用 -h 来获取帮助
# 2009-09-14 加入检测宿主系统是否存在必须命令的判断
# 2009-09-07 独立hg repo， 使脚本独立运行
# 2009-09-04 优化更新。
# 2009-08-25 加入pacman.static 在非Arch环境下也可运行本脚本制作基于Arch GNU/Linux的Live系统。
# 2009-08-24 加入ARCH选项，在i686环境下可以制作x86_64的live系统，反之亦然。
# 2009-08-23 加入附加模块独立制作的支持，加入grub2的支持
# 2009-08-22 加入判断 archlive.conf的模块可以采用加#来去掉，简化更新创建archlive的步骤
# 2009-08-21 加入查找archlive.conf配置文件的文件夹层数限制
# 2009-08-13 检误，并智能化检测、判断制作配置文件archlive.conf
# 2009-08-12 将是否采用overlay个性化定制目录设置放于archlive.conf中
# 2009-08-10 修改运行目录结构，并将模块压缩方式的选择放到配置文件中
# 2009-08-10 修改判断软件仓是否已经在pacman.conf中定义的判断方式
#
APPNAME=$(basename "${0}")
export cmdline=$@
time=5

# 消息提示风格, 默认 有颜色输出
export COLOR=y

#START_DIR=$(pwd)
START_DIR=.
# 两种方式均可

. ${START_DIR}/lib/functions

case "$cmdline" in *-h*) usage 1;; esac
[ "$EUID" != "0" ] && (error "错误: 必须以root用户运行本制作脚本." && exit 1)

start_notice
check_host_aufs
check_host_cmd
host_isnot_arch

export QUIET
export FORCE
export CONFIG_FILE
export PROFILE_DIR
export ARCH
export arch
export CPIOCONFIG
export ZIP_MODE
export CACHE
export BOOT
export CDNAME

# 检测输入的参数
while getopts 'P:c:A:i:b:z:C:nfvh' arg; do
   case "${arg}" in
	c) cdname="${OPTARG}" ;;
	P) config_file="${OPTARG}" ;;
	A) arch1="${OPTARG}" ;;
	i) boot_kernel_config1="${OPTARG}" ;;
	b) boot="${OPTARG}" ;;
	z) zip_mode="${OPTARG}" ;;
	C) cache_dir="${OPTARG}" ;;
	f) FORCE="y" ;;
	v) QUIET="n" ;;
        h|?) usage 0 ;;
        *) error "无效参数 '${arg}'"; usage 1 ;;
   esac
done

shift $(($OPTIND - 1))

CMD_NAME="${1}"

# 载入配置文件、配置相关参数
check_config

# 检查命令有效性
case "${CMD_NAME}" in
   prepare) WORK_DIR="${2}";;
   solemodules) WORK_DIR="${2}";;
   install) WORK_DIR="${2}";;
   remount) WORK_DIR="${2}";;
   clean) WORK_DIR="${2}";;
   squash) WORK_DIR="${2}";;
   overlay) WORK_DIR="${2}";;
   bootimage) WORK_DIR="${2}";;
   img) WORK_DIR="${2}"; IMGNAME="${3}";;
   all) WORK_DIR="${2}"; IMGNAME="${3}";;
   *) error "无效命令 '${CMD_NAME}'"; usage 1 ;;
esac

case $cmdline in *-f*) FORCE="y";; esac

# 实际需要的模块 (去掉#)
MODULES=$(echo $MODULES | tr -s " " "\n" | grep -v "#")
EXTRAMODULES=$(echo $EXTRAMODULES | tr -s " " "\n" | grep -v "#")

#-------------------------------------------------------------------
LIBS="${START_DIR}/lib/mkinitcpio/initcpio/liblinuxlive"
MKINITCPIO="${START_DIR}/lib/mkinitcpio"
PACMAN_STATIC="${START_DIR}/lib/bin/pacman.static"
#-------------------------------------------------------------------

# 如果archlive.conf没有定义则采用如下默认配置

# 包含内核的模块, 如果archlive.conf中没有定义则默认为list中的第一个
if [ "x${KERNEL_MOD}" = "x" ]; then
   KERNEL_MOD="$(ls -1 ${PROFILE_DIR}/$LISTDIR | head -1)"
fi
if [ "x$QUIET" = "x" ]; then QUIET="y"; fi
if [ "x$FORCE" = "x" ]; then FORCE="y"; fi
if [ "x$CDNAME" = "x" ]; then CDNAME="archlive"; fi
if [ "x$CACHE" = "x" ]; then
   if [ -f /etc/pacman.conf ]; then
	CACHE="$(grep "^CacheDir" /etc/pacman.conf | awk -F '=' '{print $2}')"
   else
	plain "系统不存在pacman.conf，宿主系统不是Arch, 将创建软件包缓存位置/var/cache/pacman/pkg"
	CACHE="/var/cache/pacman/pkg"
   fi
fi
[ -d ${CACHE} ] || mkdir -p ${CACHE}

if [ "x$PUBLISHER" = "x" ]; then PUBLISHER="http://archlive.googlecode.com/"; fi
if [ "x$AUTHOR" = "x" ]; then AUTHOR="Carbon Jiao"; fi

# 检测模块压缩模式
case ${ZIP_MODE} in 
   1) MKSQUASHFS="${START_DIR}/lib/bin/mksquashfs4"
      EXT="sqfs"
      OPTION="-nolzma"
      ;;
   2) MKSQUASHFS="${START_DIR}/lib/bin/mksquashfs4"
      EXT="lzm"
      OPTION="lzma"
      ;;
   *) MKSQUASHFS="${START_DIR}/lib/bin/mksquashfs4"
      EXT="sqfs"
      OPTION=""
      ;;
esac


# 目标系统是否支持必须的文件系统：aufs squashfs
if [ "${KERNEL_KEY_FS_INCLUDED}" = "y" ]; then
	SUPPORT_ESSENTIAL_MODULES="y"
else
	SUPPORT_ESSENTIAL_MODULES="n"
fi

case $IMG_TYPE in
	iso*) IMG_TYPE="iso" ;;
	img*) IMG_TYPE="iso"; warn "目前均mkisofs来创建为iso启动格式，可以采用dd或者UltraISO写到U盘" ;;
	disk*) IMG_TYPE="iso"; warn "目前均mkisofs来创建为iso启动格式，可以采用dd或者UltraISO写到U盘" ;;
	cd*) IMG_TYPE="iso" ;;
	*) IMG_TYPE="iso"; warn "无效的目标类型，采用默认的iso";;
esac

if [ "x${WORK_DIR}" = "x" ]; then
#   plain "命令行没有指定工作目录，查看配置文件中是否有设置的工作目录"
   WORK_DIR=$(grep "WORK_DIR=" ${CONFIG_FILE} | cut -d "=" -f2)
   if [ "x${WORK_DIR}" = "x" ]; then 
	WORK_DIR="/Archlive_working_dir"
	#warn "配置文件也没有指定工作目录，采用默认设置${WORK_DIR}"
   fi
fi
WORK_DIR=${WORK_DIR%/}

if [ "x${IMGNAME}" = "x" ]; then
	IMGNAME="${WORK_DIR}/archlive-${arch}-$(date +%F).${IMG_TYPE}"
elif [ "$(basename ${IMGNAME})" = "${IMGNAME}" ]; then
	IMGNAME="${WORK_DIR}/${IMGNAME}"
fi

###---------------------------程序运行相关目录设置--------------------------------
# 制作的Archlive镜像目录（该文件夹下所有内容全部包含于目标镜像）
IMGROOT="${WORK_DIR}/${arch}-img"

# 制作Archlive的aufs文件系统挂载点
UNION="${WORK_DIR}/union"

# 模块软件清单（包含版本信息）目录
PACKLIST="${WORK_DIR}/${arch}-list"
# .new  存放目前对应模块最新软件清单（需要安装的）
# .old  存放运行本脚本前对应模块的软件清单
# .list 存放对应模块更新详情
# mod_模块.new 为最终 对应模块的实际软件清单

# 模块安装日志存放目录
BUILDLOG="${WORK_DIR}/${arch}-log"
# 该目录下
#  -errors.log 为对应模块安装错误日志

# 附加独立程序模块EXTRAMODULES的存放位置（sqfs或者lzm模块）
EXTRA_MODULES_TARGET="${IMGROOT}/${CDNAME}/modules/extra_modules"

# 不需要的模块备份位置
NO_NEED_MODULES_DIR="${WORK_DIR}/${NO_NEED_MODULES}"

# archlive.conf 中定义的 EXTRAPACKAGEDIR 的存放位置
EXTRA_PACKAGE_TARGET="${IMGROOT}/${CDNAME}/modules/extra_package"

# 需要特别初始化的配置
function init_config ()
{
	# 装载archlive软件仓等相关配置
	PACMANCONF="${WORK_DIR}/pacman_${ARCH}.conf"

	# 清单文件夹
	LISTDIR=list-${ARCH}

	# Arch软件仓数据库存放位置
	SYNC="${WORK_DIR}/$ARCH-sync"

	# 更新安装时候的软件缓存位置
	[ -f $PACMANCONF ] && sed -i "s%.*CacheDir.*%CacheDir = $CACHE%g" $PACMANCONF

	#archlive最终软件清单
	PKGFILE="${WORK_DIR}/$ARCH-packages.list"

	# 制作的Archlive的模块实际存放目录（安装的实际位置）
	MODULEDIR="${WORK_DIR}/${ARCH}-modules"

	# archlive.conf中定义的EXTRAMODULES的安装位置
	EXTRA_MODULES_INS_DIR="${MODULEDIR}/extra_modules"

	# 内核模块的安装位置   比如 ${WORK_DIR}/modules/0_000_core  制作启动内核镜像需要此变量
	INSTROOT="${MODULEDIR}/$(basename ${KERNEL_MOD} .list)"

	# 附加模块清单（最终，包含版本信息）
	EXTRA_MODULE_LIST=${PACKLIST}/${ARCH}-extra_module.list

	# 将宿主系统可用执行程序排序输出到工作目录下
	[ -d ${PACKLIST} ] && sort /tmp/cmd_list > "${PACKLIST}/host_cmd_list"

	# 安装清理删除的文件、目录的备份目录
	CLEANED_BACKUP_DIR="${WORK_DIR}/${ARCH}-Cleaned_files_backup"
}

sleep $time

init_config

# 列出任务描述
case "${CMD_NAME}" in
   prepare) DESCRIPTION="准备工作: 准备安装程序，准备镜像目录结构，Overlay目录等.";;
   solemodules) DESCRIPTION="在工作目录 ${WORK_DIR} 下制作独立程序模块：安装后用 mksquash 压缩." ;; 
   install) DESCRIPTION="将程序清单或者附加模块指定的程序安装到工作目录${UNION},放置于${MODULEDIR}." ;;
   clean) DESCRIPTION="根据配置文件清理 ${MODULEDIR}，删除或者移动运行时无需的文件." ;;
   remount) DESCRIPTION="将之前制作的模块文件夹重新可写挂载到$UNION." ;;
   squash) DESCRIPTION="将 ${MODULEDIR} 目录用 mksquash 压缩为模块." ;;
   overlay) DESCRIPTION="准备Overlay目录到 ${WORK_DIR}下，并用 mksquash 压缩为模块." ;;
   bootimage) DESCRIPTION="从指定目录 ${MODULEDIR}/${KERNEL_MOD} 制作 $CDNAME 启动内核" ;;
   img) DESCRIPTION="将目录 ${IMGROOT} 制作为 ${IMG_TYPE} 镜像." ;;
   all) DESCRIPTION="根据配置文件或者参数指定的设置制作${IMGNAME/*\//}" ;;
esac

#-------------------------------------------------------------------
echo ""
green "  输入的选项及命令: $cmdline"
green "  执行的动作： ${DESCRIPTION}"
echo "" "-----------------------------------------------------------------------------"
hgreen "               *** ${APPNAME} 程序设置结果： ***"
echo ""
echo "  宿主系统是否是Arch GNU/Linux:  ${ARCH_HOST} "
echo "        ${APPNAME}程序执行操作:  ${CMD_NAME}"
echo "          Archlive定制配置文件:  ${CONFIG_FILE}"
echo "            Archlive配置文件夹:  ${PROFILE_DIR}"
if [ "$arch" = "dul" ]; then
echo "                      目标系统:  双模式(i686 + x86_64)"
else
echo "                      目标系统:  ${arch}"
fi
echo "                      工作目录:  ${WORK_DIR}"
if [ "${CMD_NAME}" = "prepare" -o "${CMD_NAME}" = "install" -o "${CMD_NAME}" = "all" ]; then
echo "        目标镜像源文件存放目录:  ${IMGROOT} "
fi
if [ "${CMD_NAME}" = "install" -o "${CMD_NAME}" = "all" ]; then
echo "                软件包缓存目录:  ${CACHE} "
fi
if [ "$arch" != "dul" ]; then
echo "               pacman.conf文件:  ${PACMANCONF}"
fi
if [ "${CMD_NAME}" = "img" -o "${CMD_NAME}" = "all" ]; then
echo "                  目标镜像类型:  ${IMG_TYPE}"
echo "                    目标镜像名:  ${IMGNAME}"
echo "                      启动程序:  ${BOOT}"
fi
if [ "${CMD_NAME}" = "squash" -o "${CMD_NAME}" = "solemodules" -o "${CMD_NAME}" = "all" ]; then
echo "                      模块类型:  ${EXT}"
fi
if [ "${CMD_NAME}" = "bootimage" -o "${CMD_NAME}" = "all" ]; then
echo "  Archlive启动内核镜像配置文件:  ${CPIOCONFIG}"
fi
if [ "${CMD_NAME}" = "bootimage" -o "${CMD_NAME}" = "all" ]; then
echo "            包含内核程序的模块:  ${KERNEL_MOD}"
fi
if [ "${CMD_NAME}" = "install" -o "${CMD_NAME}" = "all" ]; then
echo "                      是否覆盖:  ${FORCE}"
fi
if [ "${CMD_NAME}" = "install" -o "${CMD_NAME}" = "squash" -o "${CMD_NAME}" = "all" ]; then
echo " 需要制作的模块为: $(echo ${MODULES} | tr -s "\n" " ")"
fi
if [ "${CMD_NAME}" = "install" -o "${CMD_NAME}" = "squash" -o "${CMD_NAME}" = "solemodules" -o "${CMD_NAME}" = "all" ]; then
echo "  需要制作的附加独立程序模块为:  $(echo ${EXTRAMODULES} | tr -s "\n" " ")"
fi
echo "                不显示制作详情:  ${QUIET}"
echo ""
hgreen "***如果以上不是你想要的，Ctrl+C 终止程序的执行，并检测相关配置文件.***"
echo -n "                            "; yellow "5 秒后开始程序"
echo "-----------------------------------------------------------------------------"
sleep $time
echo ""

function second_confirm ()
{
    echo "------------------------------ 目前工作状态 ----------------------------------"
    echo "                           ARCH:  ${ARCH}"
    echo "               pacman.conf文件:  ${PACMANCONF}"
    hgreen "***如果以上不是你想要的，Ctrl+C 终止程序的执行，并检测相关配置文件.***"
    echo -n "                            "; yellow "5 秒后开始程序"
    echo "-----------------------------------------------------------------------------"
    sleep $time
}

msg "现在开始执行 ${APPNAME} ${CMD_NAME}"
echo ""
###--------------------------------------主程序调用------------------------------------------------------
case ${CMD_NAME} in
	prepare) 
		check_host_cmds find grep sed wget gettext openssl
		# gettext openssl 被create_repo需要
		cmd_prepare ;;
	install)
		check_host_cmds awk diff depmod find grep sed chroot 
		if [ "$arch" = "dul" ]; then
			export ARCH="i686"; init_config; second_confirm; cmd_install;
			export ARCH="x86_64"; init_config; second_confirm; cmd_install;
		else
			init_config; second_confirm; cmd_install;
		fi
		;;
	clean)
		check_host_cmds find 
		if [ "$arch" = "dul" ]; then
			export ARCH="i686"; init_config; second_confirm; cmd_clean;
			export ARCH="x86_64"; init_config; second_confirm; cmd_clean;
		else
			init_config; second_confirm; cmd_clean;
		fi
		;;
	remount)
		check_host_cmds tee
		if [ "$arch" = "dul" ]; then
			error "重新挂载必须用 -A 来指定 i686 或者 x86_64"
			usage 1
		else
			init_config; cmd_re_mount_all;
		fi
		;;
	solemodules)
		check_host_cmds sed find grep depmod
		if [ "$arch" = "dul" ]; then
			export ARCH="i686"; init_config; second_confirm; prepare_pacman; cmd_extramodules_install; cmd_extramodules_squash;
			export ARCH="x86_64"; init_config; second_confirm; prepare_pacman; cmd_extramodules_install; cmd_extramodules_squash;
		else
			init_config; second_confirm; prepare_pacman; cmd_extramodules_install; cmd_extramodules_squash;
		fi
		;;
 	squash)
		check_host_cmds sed find depmod
		if [ "$arch" = "dul" ]; then
			export ARCH="i686"; init_config; second_confirm; cmd_squash;
			export ARCH="x86_64"; init_config; second_confirm; cmd_squash;
		else
			init_config; second_confirm; cmd_squash; 
		fi
		;;
	overlay)
		check_host_cmds find grep sed wget
		prepare_overlay; cmd_overlay_sqfs;
		;;
	bootimage)
		check_host_cmds find grep sed cut depmod
		if [ "$arch" = "dul" ]; then
			export ARCH="i686"; init_config; second_confirm; cmd_boot_kernel_image;
			export ARCH="x86_64"; init_config; second_confirm; cmd_boot_kernel_image;
		else
			init_config; second_confirm; cmd_boot_kernel_image; 
		fi
		;;
	img)
		check_host_cmds mkisofs find isosize 
		init_config; cmd_img;
		;;
	all)
		check_host_cmds find grep sed wget gettext openssl awk mkisofs isosize diff depmod chroot
		cmd_prepare;
		if [ "$arch" = "dul" ]; then
			export ARCH="i686"; init_config; second_confirm; cmd_install; cmd_clean; cmd_squash; cmd_boot_kernel_image;
			export ARCH="x86_64"; init_config; second_confirm; cmd_install; cmd_clean; cmd_squash; cmd_boot_kernel_image;
		else
			init_config; second_confirm; cmd_install; cmd_clean; cmd_squash; cmd_boot_kernel_image;
		fi
		cmd_img;
		;;
	*) error "错误的命令" && usage 1 ;;
esac

msg "程序执行 ${APPNAME} $cmdline 结束!"
